using System;
using System.Drawing;
using System.Collections.Generic;
using Clandestine.Graphics;
using Clandestine.Engines.Field;

namespace Shlick
{
	using Object = Clandestine.Engines.Field.Object;

	public class ObjectSelectTool : Tool	
	{
		public override string Name { get { return "Select Objects Tool"; } } 
		public MapDocument MapDocument { get; private set; }
		public Map Map { get { return this.MapDocument.Map; } } 

		private Sprite hoverSprite;
		private bool mouseDown;
		private Point mouseDownPos; // in tile co-ordinates
		private List<Object> selectedObjects;
		
		public ObjectSelectTool(MapDocument mapDocument)
		{
			this.MapDocument = mapDocument;
			
			// Lessgo!
			hoverSprite = new Sprite(this.MapDocument.Graphics, "random/white.png");
            hoverSprite.Visible = false;
            hoverSprite.Color = new Color4(0f, 1f, 1f, 0.6f);
		}
		       
        public override void Activated(params object[] args)
        {
        }
        
        public override void Deactivated()
        {
        }
		
        public override void MouseDownLeft(int mouseX, int mouseY, int tileX, int tileY)
        {
        	if (this.MapDocument.ToolManager.ActiveLevel == null)
        		return;
        		
        	// Get rid of current selection
        	deslectObjects();
        	
        	// Start drag
        	mouseDown = true;
        	mouseDownPos = new Point(tileX, tileY);	
        	
        	// Sounds funny, but update our selection immediately incase they never drag.
        	selectObjects(tileX, tileY);
        }
        
        public override void MouseUpLeft(int mouseX, int mouseY, int tileX, int tileY)
        {
        	if (this.MapDocument.ToolManager.ActiveLevel == null)
        		return;
        		
        	// Were we dragging?
        	if (mouseDown)
        	{
        		mouseDown = false;
        		Point mouseUpPos = new Point(tileX, tileY);
        		
        		// Get new selection.
        		selectObjects(tileX, tileY); 
        		
        		// Hide the hover sprite, we're done with that bad boy now!
        		hoverSprite.Visible = false;
        	}
        }
        
        public override void MouseDownRight(int mouseX, int mouseY, int tileX, int tileY)
        {
        	if (this.MapDocument.ToolManager.ActiveLevel == null)
        		return;
        	mouseDownPos = new Point(tileX, tileY);
        
        	/* 
        	 * Two different behaviors:
        	 *	 a) We click *on* an existing selection, then bring up the menu for that
        	 *   b) We click on nothing - i.e. there is no currect selection or there is and we don't right click anything in it.
        	 */
        	bool behaviorA = false;
        	
        	// Handle (a)
        	if (selectedObjects != null && selectedObjects.Count > 0) 
        		// Have we clicked on one we've previously selected?
        		foreach (Object o in selectedObjects)
        			if (this.rectsIntersect(new Rectangle(o.Position, o.Size), new Rectangle(tileX, tileY, 0, 0)))
        			{
        				behaviorA = true;
        				break;
        			}
        		
        	// Now if (a) was true, then selectionOK will be true... handle (b)!
        	if (!behaviorA)
        		// Just update the selection
        		selectObjects(tileX, tileY);

			// Do we have anything?
			if (selectedObjects.Count == 0)
				return;

			// Make a menu and show it.
			IContextMenu menu = new ObjectMenu(this.MapDocument, selectedObjects);
			menu.Show();
			
			// Handle deleted -- menu could have deleted some from our selection
			for (int i = 0; i < selectedObjects.Count; i++)
				if (selectedObjects[i] == null)
					selectedObjects.RemoveAt(i);
        }

        public override void MouseUpRight(int mouseX, int mouseY, int tileX, int tileY)
        {
        }
        
        public override void MouseMove(int mouseX, int mouseY, int tileX, int tileY)
        {
        	if (this.MapDocument.ToolManager.ActiveLevel == null)
        		return;

        	if (mouseDown)
        	{        		
	        	// Update selection
	        	selectObjects(tileX, tileY);
	        	
	        	// Update hover sprite
	       		updateHoverSprite(tileX, tileY);
	       	}
        }
        
        private void updateHoverSprite(int tileX, int tileY)
        {
            Point currentPos = new Point(tileX, tileY);

            // get co-ords of top left of the selection - note, we can move the mouse behind the original point
            // so must handle this!
            Point topLeft = new Point();
            topLeft.X = (currentPos.X < mouseDownPos.X ? currentPos.X : mouseDownPos.X);
            topLeft.Y = (currentPos.Y < mouseDownPos.Y ? currentPos.Y : mouseDownPos.Y);

            // get co-ords of the bottom right
            Point bottomRight = new Point();
            bottomRight.X = (currentPos.X > mouseDownPos.X ? currentPos.X : mouseDownPos.X);
            bottomRight.Y = (currentPos.Y > mouseDownPos.Y ? currentPos.Y : mouseDownPos.Y);

            hoverSprite.Visible = true;
            hoverSprite.Transform.X = topLeft.X * this.Map.GridSize;
            hoverSprite.Transform.Y = topLeft.Y * this.Map.GridSize;
            hoverSprite.Transform.ScaleX = (bottomRight.X + 1 - topLeft.X) * this.Map.GridSize;
            hoverSprite.Transform.ScaleY = (bottomRight.Y + 1 - topLeft.Y) * this.Map.GridSize;
        }
        
        // updates our list of selected objects
        private void selectObjects(int tileX, int tileY)
        {
        	deslectObjects(); // clear previous
        	
          	// Find what objects are here & select.
        	selectedObjects = getObjectsInBox(new Rectangle(mouseDownPos.X, mouseDownPos.Y, tileX - mouseDownPos.X, tileY - mouseDownPos.Y));
        	foreach (Object o in selectedObjects)
        		o.Select();			
        }
        
        // clears out list of selected objects
        private void deslectObjects()
        {
        	if (selectedObjects != null)
        	{
        		foreach (Object o in selectedObjects)
        			o.Deselect();
        		selectedObjects.Clear();		
        	}		
        }
        
        // returns a list of objects in the given rectangle.
        private List<Object> getObjectsInBox(Rectangle rect)
        { 	
        	List<Object> objects = new List<Object>();	
        	foreach (Object o in this.MapDocument.ToolManager.ActiveLevel.Objects)
        		if (rectsIntersect(rect, new Rectangle(o.Position, o.Size)))
        			objects.Add(o);
        	return objects;
        }
        
        // whether or not the given rectangles intersect.
        private bool rectsIntersect(Rectangle rect1, Rectangle rect2)
        {
        	// Handle negative width/height
        	fixNegativeSize(ref rect1);
        	fixNegativeSize(ref rect2);
        	
        	return !(Rectangle.Intersect(rect1, rect2).IsEmpty);
        }
        
        // eg. converts (5, 5, -2, -2) to (3, 3, 2, 2)
        private void fixNegativeSize(ref Rectangle rect)
        {
        	if (rect.Width < 0)
        	{
        		rect.Width = Math.Abs(rect.Width);
        		rect.X -= rect.Width;
        	}
        	if (rect.Height < 0)
        	{
        		rect.Height = Math.Abs(rect.Height);
        		rect.Y -= rect.Height;
        	}
        }
	}
}

